package cn.bugDance.domain.strategy.service.algorithm.impl;

import cn.bugDance.domain.strategy.model.entity.AwardRateRangeEntity;
import cn.bugDance.domain.strategy.model.entity.RuleConfigEntity;
import cn.bugDance.domain.strategy.model.entity.StrategyAwardEntity;
import cn.bugDance.domain.strategy.model.entity.StrategyEntity;
import cn.bugDance.domain.strategy.model.valobj.StrategyConstant;
import cn.bugDance.domain.strategy.repository.IStrategyRepository;
import cn.bugDance.domain.strategy.service.algorithm.IAlgorithm;
import cn.bugDance.types.common.Constants;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.SecureRandom;
import java.util.*;
import java.util.stream.Collectors;

@Component("algorithmOn")
public class AlgorithmOn implements IAlgorithm {

    @Resource
    private IStrategyRepository strategyRepository;

    @Override
    public void algorithmAssemble(Long strategyId, Date endTime) {
        StrategyEntity strategyEntity = strategyRepository.queryStrategyByStrategyId(strategyId);
        List<StrategyAwardEntity> strategyAwardEntities = strategyRepository.queryStrategyAwardByStrategyId(strategyId);
        if(null == strategyEntity || null == strategyAwardEntities || strategyAwardEntities.isEmpty()) return;

        cacheRaffleRate(strategyAwardEntities, strategyEntity, endTime);
    }

    @Override
    public Integer algorithmQuery(Long strategyId, Integer level) {
        String cacheRateTableKey = Constants.Redis.STRATEGY_RATE_TABLE + strategyId + ":" + level;
        String cacheRateLevelKey = Constants.Redis.STRATEGY_RATE_LEVEL + strategyId + ":" + level;

        Integer rateMapLen = strategyRepository.queryRateTableLen(cacheRateTableKey);
        Integer rateRange = strategyRepository.queryCacheRateRangeOn(cacheRateLevelKey);
        if(rateMapLen < 8){
            return sequentialSearch(rateRange, cacheRateTableKey);
        }
        else if(rateMapLen < 16){
            return binarySearch(rateRange, cacheRateTableKey);
        }
        else{
            return threadSearch(rateRange, cacheRateTableKey);
        }
    }

    @Override
    public Integer algorithmQuery(Long strategyId) {
        return 0;
    }

    private void cacheRaffleRate(List<StrategyAwardEntity> strategyAwardEntities, StrategyEntity strategyEntity, Date endTime){
        if(strategyEntity.isExistRuleModel(StrategyConstant.StrategyRule.RULE_WEIGHT)){
            RuleConfigEntity ruleConfigEntity = strategyRepository.queryRuleConfigRule(strategyEntity.getStrategyId(), StrategyConstant.StrategyRule.RULE_WEIGHT);
            Map<Integer, List<Integer>> levelMap = ruleConfigEntity.ruleConfigSplit();
            if(levelMap.isEmpty()) return;

            Set<Integer> levelSet = levelMap.keySet();
            for(Integer level : levelSet){
                List<Integer> awardIds = levelMap.get(level);
                List<StrategyAwardEntity> strategyAwardEntitiesCopy = strategyAwardEntities;
                strategyAwardEntitiesCopy = strategyAwardEntitiesCopy.stream().filter(strategyAwardEntity -> awardIds.contains(strategyAwardEntity.getAwardId())).collect(Collectors.toList());
                String cacheRateTableKey = Constants.Redis.STRATEGY_RATE_TABLE + strategyEntity.getStrategyId() + ":" + level;
                String cacheRateLevelKey = Constants.Redis.STRATEGY_RATE_LEVEL + strategyEntity.getStrategyId() + ":" + level;
                cacheRaffleRate(strategyAwardEntitiesCopy, cacheRateTableKey, cacheRateLevelKey);
            }
        }
        String cacheRateTableKey = Constants.Redis.STRATEGY_RATE_TABLE + strategyEntity.getStrategyId() + ":" + Constants.Redis.STRATEGY_RATE_NO_LEVEL;
        String cacheRateLevelKey = Constants.Redis.STRATEGY_RATE_LEVEL + strategyEntity.getStrategyId() + ":" + Constants.Redis.STRATEGY_RATE_NO_LEVEL;
        List<StrategyAwardEntity> strategyAwardEntitiesCopy = strategyAwardEntities.stream().filter(strategyAwardEntity -> strategyAwardEntity.getRaffle() == 0).collect(Collectors.toList());
        cacheRaffleRate(strategyAwardEntitiesCopy, cacheRateTableKey, cacheRateLevelKey);

        cacheRaffleAwardCount(strategyAwardEntitiesCopy, endTime);
    }

    private void cacheRaffleAwardCount(List<StrategyAwardEntity> strategyAwardEntitiesCopy, Date endTime) {
        for(StrategyAwardEntity strategyAwardEntity : strategyAwardEntitiesCopy){
            String cacheAwardCountKey = Constants.Redis.STRATEGY_AWARD_COUNT + strategyAwardEntity.getStrategyId() + ":" + strategyAwardEntity.getAwardId();
            strategyRepository.storeAwardCount(cacheAwardCountKey, strategyAwardEntity.getAwardCount(), strategyAwardEntity.getStrategyId(), strategyAwardEntity.getAwardId(), endTime);
        }
    }

    private void cacheRaffleRate(List<StrategyAwardEntity> strategyAwardEntities, String cacheRateTableKey, String cacheRateLevelKey){

        BigDecimal minAwardRate = strategyAwardEntities.stream()
                .map(StrategyAwardEntity::getAwardRate)
                .min(BigDecimal::compareTo)
                .orElse(BigDecimal.ZERO);

        BigDecimal scale = BigDecimal.valueOf(convert(minAwardRate.doubleValue()));

        int from = 1;
        int to = 0;
        Map<AwardRateRangeEntity, Integer> rateTable = new HashMap<>();

        for(StrategyAwardEntity strategyAwardEntity : strategyAwardEntities){
            Integer awardId = strategyAwardEntity.getAwardId();
            BigDecimal awardRate = strategyAwardEntity.getAwardRate();

            int rateRange = awardRate.multiply(scale).setScale(0, RoundingMode.CEILING).intValue();
            to = from + (rateRange - 1);
            AwardRateRangeEntity awardRateRangeEntity = new AwardRateRangeEntity();
            awardRateRangeEntity.setFrom(from);
            awardRateRangeEntity.setTo(to);
            from = to + 1;
            rateTable.put(awardRateRangeEntity, awardId);
        }

        strategyRepository.storeToCacheOn(rateTable, to, cacheRateTableKey, cacheRateLevelKey);


    }

    private double convert(double minAwardRate){
        double rateRange = 1;
        while(minAwardRate < 1){
            minAwardRate *= 10;
            rateRange *= 10;
        }
        return rateRange;
    }

    private Integer sequentialSearch(Integer rateRange, String cacheRateTableKey){
        int random = new SecureRandom().nextInt(rateRange);
        Map<AwardRateRangeEntity, Integer> rateMap = strategyRepository.queryCacheRateMap(cacheRateTableKey);
        for(Map.Entry<AwardRateRangeEntity, Integer> entry : rateMap.entrySet()){
            AwardRateRangeEntity awardRateRangeEntity = entry.getKey();
            int from = awardRateRangeEntity.getFrom();
            int to = awardRateRangeEntity.getTo();
            if(from <= (random + 1) && to <= (random + 1)){
                return rateMap.get(awardRateRangeEntity);
            }
        }
        return -1;
    }

    private Integer binarySearch(Integer rateRange, String cacheRateTableKey){
        int random = new SecureRandom().nextInt(rateRange);
        Map<AwardRateRangeEntity, Integer> rateMap = strategyRepository.queryCacheRateMap(cacheRateTableKey);
        Set<AwardRateRangeEntity> awardRateRangeEntitiesSet = rateMap.keySet();
        List<AwardRateRangeEntity> awardRateRangeEntities = new ArrayList<>(awardRateRangeEntitiesSet);
        awardRateRangeEntities.sort(new Comparator<AwardRateRangeEntity>() {
            @Override
            public int compare(AwardRateRangeEntity o1, AwardRateRangeEntity o2) {
                return Integer.compare(o1.getFrom(), o2.getFrom());
            }
        });
        int left = 0, right = awardRateRangeEntities.size() - 1;
        while(left <= right){
            int mid = (left + right) >>> 1;
            AwardRateRangeEntity awardRateRangeEntity = awardRateRangeEntities.get(mid);
            int from = awardRateRangeEntity.getFrom();
            int to = awardRateRangeEntity.getTo();
            if(from <= (random + 1) && (random + 1) <= to){
                return rateMap.get(awardRateRangeEntity);
            }
            else if((random + 1) < from){
                right = mid - 1;
            }
            else if((random + 1) > to){
                left = mid + 1;
            }
        }
        return -1;
    }

    private Integer threadSearch(Integer rateRange, String cacheRateTableKey){
        int random = new SecureRandom().nextInt(rateRange);
        Map<AwardRateRangeEntity, Integer> rateMap = strategyRepository.queryCacheRateMap(cacheRateTableKey);
        Set<AwardRateRangeEntity> awardRateRangeEntitiesSet = rateMap.keySet();
        List<AwardRateRangeEntity> awardRateRangeEntities = new ArrayList<>(awardRateRangeEntitiesSet);
        AwardRateRangeEntity awardRateRangeEntity = strategyRepository.algorithmQueryThreadSearch(awardRateRangeEntities, random);

        return rateMap.get(awardRateRangeEntity);
    }
}
